import os

from xmindparser import xmind_to_dict
import xlwt


class XlwtSeting(object):

    @staticmethod  # 静态方法装饰器，使用此装饰器装饰后，可以直接使用类名.方法名调用（XlwtSeting.styles()），并且不需要self参数
    def template_one(worksheet):
        dicts = {"horz": "CENTER", "vert": "CENTER"}
        sizes = [15, 15, 15, 15, 40, 15, 15, 20, 25, 40, 40, 15, 15]

        se = XlwtSeting()
        style = se.styles()
        style.alignment = se.alignments(**dicts)
        style.font = se.fonts(bold=True)
        style.borders = se.borders()
        style.pattern = se.patterns(17)
        se.heights(worksheet, 0)
        for i in range(len(sizes)):
            se.widths(worksheet, i, size=sizes[i])
        return style

    @staticmethod
    def template_two():
        dicts2 = {"vert": "CENTER"}
        se = XlwtSeting()
        style = se.styles()
        style.borders = se.borders()
        style.alignment = se.alignments(**dicts2)
        return style

    @staticmethod
    def styles():
        """设置单元格的样式的基础方法"""
        style = xlwt.XFStyle()
        return style

    @staticmethod
    def borders(status=1):
        """设置单元格的边框，
        细实线:1，小粗实线:2，细虚线:3，中细虚线:4，大粗实线:5，双线:6，细点虚线:7大粗虚线:8，细点划线:9，粗点划线:10，细双点划线:11，粗双点划线:12，斜点划线:13"""
        border = xlwt.Borders()
        border.left = status
        border.right = status
        border.top = status
        border.bottom = status
        return border

    @staticmethod
    def heights(worksheet, line, size=4):
        """设置单元格的高度"""
        worksheet.row(line).height_mismatch = True
        worksheet.row(line).height = size * 256

    @staticmethod
    def widths(worksheet, line, size=11):
        """设置单元格的宽度"""
        worksheet.col(line).width = size * 256

    @staticmethod
    def alignments(wrap=1, **kwargs):
        """设置单元格的对齐方式，
        ：接收一个对齐参数的字典{"horz": "CENTER", "vert": "CENTER"}horz（水平），vert（垂直）
        ：horz中的direction常用的有：CENTER（居中）,DISTRIBUTED（两端）,GENERAL,CENTER_ACROSS_SEL（分散）,RIGHT（右边）,LEFT（左边）
        ：vert中的direction常用的有：CENTER（居中）,DISTRIBUTED（两端）,BOTTOM(下方),TOP（上方）"""
        alignment = xlwt.Alignment()

        if "horz" in kwargs.keys():
            alignment.horz = eval(f"xlwt.Alignment.HORZ_{kwargs['horz'].upper()}")
        if "vert" in kwargs.keys():
            alignment.vert = eval(f"xlwt.Alignment.VERT_{kwargs['vert'].upper()}")
        alignment.wrap = wrap  # 设置自动换行
        return alignment

    @staticmethod
    def fonts(name='宋体', bold=False, underline=False, italic=False, colour='black', height=11):
        """设置单元格中字体的样式，
        默认字体为宋体，不加粗，没有下划线，不是斜体，黑色字体"""
        font = xlwt.Font()
        # 字体
        font.name = name
        # 加粗
        font.bold = bold
        # 下划线
        font.underline = underline
        # 斜体
        font.italic = italic
        # 颜色
        font.colour_index = xlwt.Style.colour_map[colour]
        # 大小
        font.height = 20 * height
        return font

    @staticmethod
    def patterns(colors=1):
        """设置单元格的背景颜色，该数字表示的颜色在xlwt库的其他方法中也适用，默认颜色为白色
        0 = Black, 1 = White,2 = Red, 3 = Green, 4 = Blue,5 = Yellow, 6 = Magenta, 7 = Cyan,
        16 = Maroon, 17 = Dark Green,18 = Dark Blue, 19 = Dark Yellow ,almost brown), 20 = Dark Magenta,
        21 = Teal, 22 = Light Gray,23 = Dark Gray, the list goes on..."""
        pattern = xlwt.Pattern()
        pattern.pattern = xlwt.Pattern.SOLID_PATTERN
        pattern.pattern_fore_colour = colors
        return pattern


class XmindToXlsx(XlwtSeting):

    def __init__(self, name):
        """调用类时，读取xmind文件，并生成excel表格"""
        try:
            self.xm = xmind_to_dict(name)[0]['topic']
            self.xm['title'] = self.xm['title'].replace('\n', '').strip()
        except Exception as e:
            print(f"打开xmind文件失败:{e}")
            # 如果文件打开失败，后续操作无法进行，可以抛出异常或设置一个标志
            self.xm = None
            return

        self.workbook = xlwt.Workbook(encoding='utf-8')
        self.worksheet = self.workbook.add_sheet(self.xm["title"], cell_overwrite_ok=True)
        self.x = 0  # 用例写入的当前行数
        self.style = self.template_two() # 预先创建样式，避免在循环中重复创建

    def save(self, name, path="."):
        """保存表格"""
        # (这部分代码无需修改，保持原样)
        try:
            safe_name = name.replace('\n', '').replace('/', '_').replace('\\', '_').strip()
            safe_name = f"{safe_name}_{'测试用例'}"
            if not safe_name.endswith('.xls'):  # xlwt 保存为 .xls 格式
                safe_name = safe_name.replace('.xlsx', '.xls')
                if not safe_name.endswith('.xls'):
                    safe_name += ".xls"

            full_path = os.path.join(path, safe_name)

            base_path, extension = os.path.splitext(full_path)
            counter = 1
            while os.path.exists(full_path):
                full_path = f"{base_path}_{counter}{extension}"
                counter += 1

            os.makedirs(os.path.dirname(full_path), exist_ok=True)
            self.workbook.save(full_path)
            print(f"文件已保存到: {full_path}")

        except PermissionError:
            try:
                backup_path = os.path.join(os.getcwd(), safe_name)
                self.workbook.save(backup_path)
                print(f"由于权限问题，文件已保存到当前目录: {backup_path}")
            except Exception as e:
                print(f"保存文件失败: {str(e)}")
        except Exception as e:
            print(f"保存文件时出错: {str(e)}")

    def _process_prefix(self, text, prefix):
        """一个通用的去除前缀的辅助函数"""
        text = text.strip()
        if text.startswith(prefix):
            return text[len(prefix):].strip()
        return text

    def _traverse_and_write(self, node, path):
        """
        递归遍历节点并写入Excel。
        :param node: 当前XMind节点字典。
        :param path: 从根到当前节点的路径标题列表。
        """
        # 将当前节点的标题（已清理）添加到路径中
        current_path = path + [node['title'].replace('\n', '').strip()]

        # 检查是否为叶子节点（路径的终点）
        if 'topics' not in node or not node['topics']:
            # 这是一个叶子节点，代表一个完整的用例分支

            # 一个有效的测试用例路径至少需要5层: 系统 -> 前置 -> 名称 -> 步骤 -> 结果
            if len(current_path) < 5:
                print(f"警告: 路径太短，无法构成有效用例，已跳过: {' -> '.join(current_path)}")
                return

            # --- 按新规则提取数据 ---
            # 1. 从后往前提取测试用例的核心4个字段
            exce_title = self._process_prefix(current_path[-1], "预期结果：")
            step_title = self._process_prefix(current_path[-2], "操作步骤：")
            case_title = self._process_prefix(current_path[-3], "用例名称：")
            pre_title = self._process_prefix(current_path[-4], "前置条件：")

            # 2. 从前往后提取模块信息
            sys = current_path[0]
            # 使用 get 方法或者三元表达式安全地获取，避免路径太短导致 IndexError
            module_path = current_path[1:-4] # 模块路径是介于系统和前置条件之间的部分
            first_menu = module_path[0] if len(module_path) > 0 else ""
            second_menu = module_path[1] if len(module_path) > 1 else ""
            function = module_path[2] if len(module_path) > 2 else ""

            # 3. 准备其他字段
            numble = ''      # 用例编号
            priority = ''    # 用例优先级
            data = ''        # 测试数据
            rs = ''          # 实际结果
            person = ''      # 测试负责人

            # --- 写入Excel ---
            self.x += 1
            self.heights(self.worksheet, self.x, size=2)

            self.worksheet.write(self.x, 0, sys, self.style)
            self.worksheet.write(self.x, 1, first_menu, self.style)
            self.worksheet.write(self.x, 2, second_menu, self.style)
            self.worksheet.write(self.x, 3, function, self.style)
            self.worksheet.write(self.x, 4, case_title, self.style)
            self.worksheet.write(self.x, 5, numble, self.style)
            self.worksheet.write(self.x, 6, priority, self.style)
            self.worksheet.write(self.x, 7, data, self.style)
            self.worksheet.write(self.x, 8, pre_title, self.style)
            self.worksheet.write(self.x, 9, step_title, self.style)
            self.worksheet.write(self.x, 10, exce_title, self.style)
            self.worksheet.write(self.x, 11, rs, self.style)
            self.worksheet.write(self.x, 12, person, self.style)

            return # 结束这个分支的递归

        # 如果不是叶子节点，继续对其所有子节点进行递归
        for sub_topic in node['topics']:
            self._traverse_and_write(sub_topic, current_path)

    def write_excel(self, path):
        """
        生成excel文件的主方法。
        """
        if not self.xm:
            print("XMind文件未成功加载，无法生成Excel。")
            return

        # 1. 写入表头
        row0 = ["系统名称", '一级菜单', '二级菜单', '功能', '用例名称', '用例编号 ', '用例优先级', '测试数据', '前置条件', '操作步骤', '预期结果', '实际结果', '测试负责人']
        style_header = self.template_one(self.worksheet)
        for i in range(len(row0)):
            self.worksheet.write(0, i, row0[i], style_header)

        # 2. 从根节点的子节点开始递归遍历
        # (根节点本身是系统名，它的topics才是一级菜单)
        if 'topics' in self.xm and self.xm['topics']:
            for first_level_node in self.xm['topics']:
                # 初始路径包含系统名称
                self._traverse_and_write(first_level_node, [self.xm['title']])

        # 3. 保存文件
        self.save(self.xm["title"], path)

# 替换掉原来的 print_topics 函数
def print_incomplete_paths(data, full_depth=7, current_depth=1, path=None):
    """
    递归地遍历XMind数据，并打印出所有层级深度不足的路径。
    一个完整的测试用例路径（从一级菜单到预期结果）被定义为 full_depth (默认为7)。
    此函数用于帮助定位XMind中不完整的测试用例。

    :param data: 当前XMind主题节点（一个字典）。
    :param full_depth: 完整路径应有的深度。
    :param current_depth: 当前递归的深度。
    :param path: 用于追踪当前路径的列表，请勿手动传入。
    """
    # 最佳实践：避免使用可变的默认参数
    if path is None:
        path = []

    # 检查当前节点是否有子节点
    if 'topics' not in data or not data['topics']:
        return # 如果初始节点就没有子节点，直接返回

    # 遍历所有子节点
    for topic in data['topics']:
        # 清理标题并添加到路径中
        clean_title = topic['title'].replace('\n', '').strip()
        path.append(clean_title)

        # 判断当前子节点是否为“叶子节点”（即它自己没有子节点）
        if 'topics' not in topic or not topic['topics']:
            # 如果是叶子节点，检查其深度是否小于要求的完整深度
            if current_depth < full_depth:
                print(f"路径不完整 (当前层级 {current_depth}/{full_depth}): " + " -> ".join(path))
        else:
            # 如果不是叶子节点，继续向更深层递归
            print_incomplete_paths(topic, full_depth, current_depth + 1, path)

        # 回溯：处理完一个分支后，将其从路径中移除，以便正确处理它的兄弟节点
        path.pop()


# xmind内容结构必须为   标题-菜单一级-菜单二级-功能-前置条件-用例名称-执行步骤-预期结果
# 打印不是7层的完整数据链路
if __name__ == "__main__":
    try:
        # 请确保文件路径正确
        name = 'D:\\project\\hunan_high_speed\\绿通智能查验\\2025年8月上线\\绿通智能查验系统-海关集中验放场地前置查验系统.xmind'

        print("--- 开始将XMind转换为Excel ---")
        xx = XmindToXlsx(name)
        path = os.path.dirname(name)
        xx.write_excel(path)
        print("\n--- Excel文件生成完毕 ---\n")

        print("--- 开始检查XMind结构完整性 ---")
        print(f"一个完整的用例需要 7 个层级（一级菜单 -> ... -> 预期结果）。")
        print("以下是所有层级不足 7 层的路径：")
        xmind_data = xmind_to_dict(name)[0]['topic']

        # 调用调整后的函数
        print_incomplete_paths(xmind_data, full_depth=7)
        print("\n--- 检查完毕 ---")

    except FileNotFoundError:
        print(f"错误：找不到文件 {name}，请检查路径是否正确。")
    except Exception as e:
        print(f"程序执行出错: {str(e)}")